package store.lunangangster.engine.impl.entity.mgr;

import java.util.List;
import store.lunangangster.engine.ProcessEngineConfiguration;
import store.lunangangster.engine.impl.entity.ExecutionEntity;
import store.lunangangster.engine.impl.entity.ExecutionEntityImpl;
import store.lunangangster.engine.impl.entity.IdentityLinkEntity;
import store.lunangangster.engine.impl.entity.TaskEntity;
import store.lunangangster.engine.impl.entity.VariableEntity;
import store.lunangangster.engine.impl.query.ExecutionQuery;
import store.lunangangster.engine.impl.repository.EntityMatcher;
import store.lunangangster.engine.repository.ProcessDefinition;

public class ExecutionEntityManagerImpl extends AbstractEntityManagerImpl<ExecutionEntity> implements ExecutionEntityManager {

  public ExecutionEntityManagerImpl(
      ProcessEngineConfiguration processEngineConfiguration) {
    super(processEngineConfiguration);
  }

  protected EntityMatcher<ExecutionEntity> executionWithProcessDefinitionIdMatcher = (parameter, execution) -> {
    return execution.getProcessDefinitionId() != null && execution.getProcessDefinitionId().equals(parameter);
  };

  protected EntityMatcher<ExecutionEntity> executionWithProcessInstanceIdMatcher = (parameter, execution) -> {
    return execution.getProcessInstanceId() != null && execution.getProcessInstanceId().equals(parameter);
  };

  protected EntityMatcher<ExecutionEntity> executionWithParentIdMatcher = (parameter, execution) -> {
    return execution.getParentId() != null && execution.getParentId().equals(parameter);
  };

  @Override
  public Class<? extends ExecutionEntity> getManagedEntityClass() {
    return ExecutionEntityImpl.class;
  }

  @Override
  public ExecutionEntity create() {
    return new ExecutionEntityImpl();
  }

  @Override
  public ExecutionEntity createProcessInstanceExecution(ProcessDefinition processDefinition,
      String processInstanceName) {
    ExecutionEntity processInstanceExecution = create();
    processInstanceExecution.setActive(true);

    copyProcessDefinitionValuesToExecution(processDefinition, processInstanceExecution);
    processInstanceExecution.setName(processInstanceName);

    insert(processInstanceExecution);

    // after insert
    processInstanceExecution.setProcessInstanceId(processInstanceExecution.getId());
    processInstanceExecution.setRootProcessInstance(processInstanceExecution);
    processInstanceExecution.setParentId(null); // as root

    return processInstanceExecution;
  }

  @Override
  public ExecutionEntity createChildExecution(ExecutionEntity parentExecution) {
    ExecutionEntity childExecution = create();
    childExecution.setActive(true);

    copyParentExecutionValuesToExecution(parentExecution, childExecution);

    insert(childExecution);
    parentExecution.addChildExecutions(childExecution);

    return childExecution;
  }

  private void copyProcessDefinitionValuesToExecution(ProcessDefinition processDefinition, ExecutionEntity execution) {
    execution.setDeploymentId(processDefinition.getDeploymentId());
    execution.setProcessDefinitionId(processDefinition.getId());
    execution.setProcessDefinitionKey(processDefinition.getKey());
  }

  private void copyParentExecutionValuesToExecution(ExecutionEntity parentExecution, ExecutionEntity execution) {
    execution.setDeploymentId(parentExecution.getDeploymentId());
    execution.setProcessInstanceId(parentExecution.getProcessInstanceId());
    execution.setProcessDefinitionId(parentExecution.getProcessDefinitionId());
    execution.setProcessDefinitionKey(parentExecution.getProcessDefinitionKey());
    execution.setRootProcessInstance(parentExecution.getRootProcessInstance());
    execution.setParent(parentExecution);
    execution.setRootId(parentExecution.getRootId());  // inherit parent
  }

  @Override
  public ExecutionEntity createBrotherExecution(ExecutionEntity brotherExecution) {
    ExecutionEntity parent = brotherExecution.getParent() == null ? brotherExecution : brotherExecution.getParent();
    return createChildExecution(parent);
  }

  @Override
  public void deleteProcessInstanceExecution(String processInstanceId) {
    ExecutionEntity processInstance = selectById(processInstanceId);

    // get transaction flag
    if (processInstance.isDelete()) {
      return;
    }
    // keep delete order
    deleteChildExecution(processInstance);
    deleteExecution(processInstance);
  }

  @Override
  public void deleteChildExecution(ExecutionEntity parentExecution) {
    List<ExecutionEntity> childExecutions = collectionChildExecutionRecursive(parentExecution);

    // traverse reserve
    for (int i = childExecutions.size() - 1; i >= 0; i--) {
      deleteExecution(childExecutions.get(i));
    }
  }

  @Override
  public void deleteExecution(ExecutionEntity execution) {
    execution.setActive(false);

    deleteIdentityLinks(execution);
    deleteVariables(execution);
    deleteTasks(execution);
    delete(execution);
  }


  private void deleteIdentityLinks(ExecutionEntity execution) {
    IdentityLinkEntityManager identityLinkEntityManager = getIdentityLinkEntityManager();
    List<IdentityLinkEntity> identityLinkEntities = identityLinkEntityManager.selectIdentityLinkByExecutionId(execution.getId());
    for (IdentityLinkEntity identityLinkEntity : identityLinkEntities) {
      identityLinkEntityManager.delete(identityLinkEntity);
    }
  }

  private void deleteVariables(ExecutionEntity execution) {
    VariableEntityManager variableEntityManager = getVariableEntityManager();
    for (VariableEntity variableEntity : execution.getVariableEntities().values()) {
      variableEntityManager.delete(variableEntity);
    }
  }

  private void deleteTasks(ExecutionEntity execution) {
    TaskEntityManager taskEntityManager = getTaskEntityManager();
    List<TaskEntity> taskEntities = taskEntityManager.selectTaskByExecutionId(execution.getId());
    for (TaskEntity taskEntity : taskEntities) {
      taskEntityManager.delete(taskEntity);
    }
  }

  @Override
  public List<ExecutionEntity> collectionChildExecutionRecursive(ExecutionEntity parentExecution) {
    List<ExecutionEntity> childExecutions = parentExecution.getChildExecutions();
    for (ExecutionEntity child : childExecutions) {
      collectionChildExecutionRecursive(child, childExecutions);
    }

    return childExecutions;
  }

  @Override
  public List<ExecutionEntity> selectExecutionList(List<String> ids) {
    return getDbSqlSession().selectList("selectExecutionList", ids);
  }

  private void collectionChildExecutionRecursive(ExecutionEntity parentExecution, List<ExecutionEntity> childTree) {
    List<ExecutionEntity> childExecutions = parentExecution.getChildExecutions();
    if (childTree == null) {
      childTree = childExecutions;
    } else {
      childTree.addAll(childExecutions);
    }

    for (ExecutionEntity child : childExecutions) {
      collectionChildExecutionRecursive(child, childTree);
    }
  }

  @Override
  public List<ExecutionEntity> selectExecutionByCondition(ExecutionQuery query) {
    return (List<ExecutionEntity>) getDbSqlSession().selectList("selectExecutionByCondition", query);
  }

  @Override
  public List<ExecutionEntity> selectExecutionByProcessDefinitionId(String processDefinitionId) {
    return selectList("selectExecutionByProcessDefinitionId", processDefinitionId,
        executionWithProcessDefinitionIdMatcher);
  }

  @Override
  public List<ExecutionEntity> selectChildExecutionByProcessInstanceId(String processInstanceId) {
    return selectList("selectChildExecutionByProcessInstanceId", processInstanceId,
        executionWithProcessDefinitionIdMatcher);
  }

  @Override
  public List<ExecutionEntity> selectChildExecutionByParentId(String parentId) {
    return selectList("selectChildExecutionByParentId", parentId, executionWithParentIdMatcher);
  }
}
